package org.glut.wechat.videochat.handler;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.websocket.OnMessage;
import lombok.extern.slf4j.Slf4j;
import org.glut.wechat.videochat.domain.SignalingMessage;
import org.glut.wechat.videochat.service.RoomService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.standard.StandardWebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.HashMap;

@Component
@Slf4j
public class SignalingHandler extends TextWebSocketHandler {
    @Autowired
    private RoomService roomService;

    // 存储WebSocket会话
    private final ConcurrentMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();  // 线程安全
    private final ObjectMapper objectMapper = new ObjectMapper(); // json序列化工具

    /**
     * WebSocket连接建立
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Map<String, String> params = getQueryParams(session.getUri().getQuery());  // 解析URL参数
        String roomId = params.getOrDefault("roomId", "default_room"); // 默认房间为default_room
        String userId = params.get("userId");

        // 重建默认连接
        if (params.containsKey("purpose") && "reconnect".equals(params.get("purpose"))) {
            session.getAttributes().put("isDefaultConnection", true);  // 默认连接
            log.info("用户[{}]重建默认连接成功", userId);

            // 发送状态同步消息
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(
                    Map.of(
                            "type", "sync",
                            "data", Map.of(
                                    "roomId", "default_room",
                                    "status", "idle"
                            )
                    )
            )));
        }

        // 清理旧连接
        WebSocketSession oldSession = sessions.get(userId);
        if (oldSession != null && oldSession.isOpen()) {
            oldSession.close();
        }

        sessions.put(userId, session);  // 存储新连接
        roomService.joinRoom(roomId, userId);  // 用户加入房间
        log.info("用户[{}]加入房间[{}]", userId, roomId);
    }

    /**
     * 处理接收到的WebSocket消息转发给同房间其他用户
     * 前端调用：ws.send(JSON.stringify({type: "invite", ...}))
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        SignalingMessage msg = objectMapper.readValue(message.getPayload(), SignalingMessage.class);  // 反序列化消息

        // 处理重连请求
        if ("join".equals(msg.getType()) &&
                msg.getData() != null &&
                Boolean.TRUE.equals(msg.getData().get("reconnect"))) {

            log.info("用户[{}]重建默认连接", msg.getSenderId());

            // 发送验证消息
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(
                    Map.of(
                            "type", "connection_verified",
                            "data", Map.of(
                                    "status", "idle",
                                    "timestamp", System.currentTimeMillis()
                            )
                    )
            )));

            // 发送Ping帧，保持连接活跃
            try {
                session.sendMessage(new PingMessage(ByteBuffer.wrap("RECONNECT".getBytes())));
            } catch (Exception e) {
                session.sendMessage(new TextMessage(
                        objectMapper.writeValueAsString(
                                Map.of("type", "ping", "data", "RECONNECT")
                        )
                ));
            }
        }

        // 处理通话结束消息
        if ("call_ended".equals(msg.getType())) {
            log.info("call_ended");
            Set<String> members = roomService.getRoomMembers(msg.getRoomId())
                    .stream()
                    .filter(memberId -> !memberId.equals(msg.getSenderId()))
                    .collect(Collectors.toSet());

            for (String memberId : members) {
                WebSocketSession target = sessions.get(memberId);
                if (target != null && target.isOpen()) {
                    try {
                        target.sendMessage(message);
                        log.info("已向用户[{}]转发通话结束通知", memberId);
                    } catch (IOException e) {
                        log.error("结束通知转发失败", e);
                    }
                }
            }
            handleLeaveMessage(msg);

            return;
        }

        // 处理通话邀请消息
        if ("invite".equals(msg.getType()) || "invitation".equals(msg.getType())) {
            String targetUserId = msg.getTargetUserId();  // 获取接收方id
            String currentRoom = roomService.getUserRoom(targetUserId);  // 获取接收方当前所在房间

            // 检查是否是有效的通话房间，排除default_room，不有效直接拒绝
            if (currentRoom != null
                    && !currentRoom.equals("default_room")
                    && !currentRoom.equals(msg.getRoomId())) {

                log.warn("用户[{}]正在房间[{}]通话，拒绝新邀请", targetUserId, currentRoom);

                WebSocketSession sender = sessions.get(msg.getSenderId());
                if (sender != null && sender.isOpen()) {  // 发送方id不是null且处于打开状态
                    sender.sendMessage(new TextMessage(
                            objectMapper.writeValueAsString(
                                    Map.of(
                                            "type", "target_busy",  // 表示对方忙线
                                            "reason", "target_busy",
                                            "currentRoom", currentRoom, // 返回接收方所在房间
                                            "inviteRoom", msg.getRoomId()
                                    )
                            )
                    ));
                }
                return;
            }

            handleInvitation(msg); // 统一处理邀请逻辑
        }

        // 处理通话离开消息
        if ("leave".equals(msg.getType())) {
            handleLeaveMessage(msg);
            return;
        }

        // 转发给同房间其他用户
        roomService.getRoomMembers(msg.getRoomId()).stream()  // 获取房间成员set流
                .filter(memberId -> !memberId.equals(msg.getSenderId()))  // 过滤发送者
                .forEach(memberId -> {  // 遍历处理每个成员
                    WebSocketSession target = sessions.get(memberId);  // 获取目标会话
                    if (target != null && target.isOpen()) {  // 检查会话有效性
                        try {
                            target.sendMessage(message);  // 转发消息
                            log.debug("转发消息: {} -> {}", msg.getSenderId(), memberId);
                        } catch (IOException e) {
                            log.error("消息转发失败", e);
                        }
                    }
                });
    }

    /**
     * 处理邀请消息
     */
    private void handleInvitation(SignalingMessage msg) throws IOException {
        // 验证必要字段
        if (msg.getTargetUserId() == null || msg.getRoomId() == null) {
            log.warn("无效的邀请消息: {}", msg);
            return;
        }

        // 查找目标用户会话
        WebSocketSession targetSession = sessions.get(msg.getTargetUserId());
        if (targetSession == null || !targetSession.isOpen()) {
            log.warn("目标用户[{}]不在线", msg.getTargetUserId());
            return;
        }

        // 构造标准化邀请通知
        SignalingMessage invitation = new SignalingMessage();
        invitation.setType("invitation");
        invitation.setRoomId(msg.getRoomId());
        invitation.setSenderId(msg.getSenderId());
        invitation.setData(Map.of(
                "callerId", msg.getSenderId(),
                "callType", msg.getCallType() != null ? msg.getCallType() : "video",
                "timestamp", System.currentTimeMillis()
        ));

        // 发送邀请
        targetSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(invitation)));
        log.info("邀请转发成功: {} -> {}", msg.getSenderId(), msg.getTargetUserId());
    }

    /**
     * 处理离开消息
     *
     * @param msg
     */
    private void handleLeaveMessage(SignalingMessage msg) {
        roomService.leaveRoom(msg.getRoomId(), msg.getSenderId());   // 清理Redis
        log.info("{}已离开房间", msg.getSenderId());
    }

    /**
     * 解析URL参数
     *
     * @param query
     * @return
     */
    private Map<String, String> getQueryParams(String query) {
        return Arrays.stream(query.split("&"))  // 拆分键值对
                .map(pair -> pair.split("="))  // 分割键和值
                .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));  // 收集为Map
    }
}
